﻿using System.Reflection;

namespace System.NETool;

public static class DateTimeEx
{
    /// <summary>
    /// 日期连接符
    /// </summary>
    public const string DateLink = "-";

    /// <summary>
    /// 日期连接符
    /// </summary>
    public const string DateLinkEx = "/";

    /// <summary>
    /// 时间连接符
    /// </summary>
    public const string TimeLink = ":";

    /// <summary>
    /// 日期(包含毫秒) - 格式化字符串 yyyy-MM-dd HH:mm:ss.fff
    /// </summary>
    public const string DateTimeFormatEx = "yyyy-MM-dd HH:mm:ss.fff";

    /// <summary>
    /// 日期 - 格式化字符串 yyyy-MM-dd HH:mm:ss
    /// </summary>
    public const string DateTimeFormat = "yyyy-MM-dd HH:mm:ss";

    /// <summary>
    /// 年月日 - 格式化字符串 yyyy-MM-dd
    /// </summary>
    public const string DateFormat = "yyyy-MM-dd";

    /// <summary>
    /// 时分秒 - 格式化字符串 HH:mm:ss
    /// </summary>
    public const string TimeFormat = "HH:mm:ss";

    /// <summary>
    /// 起始日期，UTC时间，1970-01-01 00：00：00起始
    /// </summary>
    public static readonly DateTime Jan1St1970 = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    /// <summary>
    /// 去除格式化字符串中的分隔符
    /// </summary>
    /// <param name="format">格式化字符串</param>
    /// <returns>去除分隔符后的字符串</returns>
    private static string TrimLink(string format)
    {
        return format.Replace(DateLink, "").Replace(DateLinkEx, "").Replace(TimeLink, "").Replace(".", "").Replace(" ", "").Trim();
    }

    /// <summary>
    /// yyyy-MM-dd HH:mm:ss (2001-02-03 04:05:06)
    /// </summary>
    /// <param name="this">DateTime</param>
    /// <param name="withLink">是否包含连接符</param>
    /// <returns>格式化后的字符串</returns>
    public static string DateTimeString(this DateTime @this, bool withLink = true)
    {
        return @this.ToString(withLink ? DateTimeFormat : TrimLink(DateTimeFormat));
    }

    /// <summary>
    /// yyyy-MM-dd HH:mm:ss.fff (2001-02-03 04:05:06.789)
    /// </summary>
    /// <param name="this">DateTime</param>
    /// <param name="withLink">是否包含连接符</param>
    /// <returns>格式化后的字符串</returns>
    public static string DateTimeExString(this DateTime @this, bool withLink = true)
    {
        return @this.ToString(withLink ? DateTimeFormatEx : TrimLink(DateTimeFormatEx));
    }

    /// <summary>
    /// yyyy-MM-dd (2001-02-03)
    /// </summary>
    /// <param name="this">DateTime</param>
    /// <param name="withLink">是否包含连接符</param>
    /// <returns>格式化后的字符串</returns>
    public static string DateString(this DateTime @this, bool withLink = true)
    {
        return @this.ToString(withLink ? DateFormat : TrimLink(DateFormat));
    }

    /// <summary>
    /// HH:mm:ss (04:05:06)
    /// </summary>
    /// <param name="this">DateTime</param>
    /// <param name="withLink">是否包含连接符</param>
    /// <returns>格式化后的字符串</returns>
    public static string TimeString(this DateTime @this, bool withLink = true)
    {
        return @this.ToString(withLink ? TimeFormat : TrimLink(TimeFormat));
    }

    /// <summary>
    /// yyyy-MM (2001-02)
    /// </summary>
    /// <param name="this">DateTime</param>
    /// <param name="withLink">是否包含连接符</param>
    /// <returns>格式化后的字符串</returns>
    public static string YearMonthString(this DateTime @this, bool withLink = true)
    {
        return @this.ToString(withLink ? "yyyy-MM" : "yyyyMM");
    }

    /// <summary>
    /// yyyy (2001)
    /// </summary>
    /// <param name="this">DateTime</param>
    /// <returns>格式化后的字符串</returns>
    public static string YearString(this DateTime @this)
    {
        return @this.ToString("yyyy");
    }

    /// <summary>
    /// 计算当前月份有多少天
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当前月份的天数</returns>
    public static int DaysInMonth(this DateTime @this)
    {
        return DateTime.DaysInMonth(@this.Year, @this.Month);
    }

    /// <summary>
    /// 获取当天的开始时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当天的开始时间</returns>
    public static DateTime StartOfDay(this DateTime @this)
    {
        return @this.SetTime(0, 0, 0, 0);
    }

    /// <summary>
    /// 获取当天的结束时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当天的结束时间</returns>
    public static DateTime EndOfDay(this DateTime @this)
    {
        return @this.SetTime(23, 59, 59, 999);
    }

    /// <summary>
    /// 获取当天的中午时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当天的中午时间</returns>
    public static DateTime Noon(this DateTime @this)
    {
        return @this.SetTime(12, 0, 0);
    }

    /// <summary>
    /// 获取当天的午夜时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当天的午夜时间</returns>
    public static DateTime Midnight(this DateTime @this)
    {
        return @this.SetTime(0, 0, 0, 0);
    }

    /// <summary>
    /// 获取当前月份的第一天
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当前月份的第一天</returns>
    public static DateTime StartOfMonth(this DateTime @this)
    {
        return new DateTime(@this.Year, @this.Month, 1).StartOfDay();
    }

    /// <summary>
    /// 获取当前月份的第一天，并指定星期几
    /// </summary>
    /// <param name="this">日期</param>
    /// <param name="dayOfWeek">指定的星期几</param>
    /// <returns>当前月份的第一天</returns>
    public static DateTime StartOfMonth(this DateTime @this, DayOfWeek dayOfWeek)
    {
        var dt = @this.StartOfMonth();
        while (dt.DayOfWeek != dayOfWeek)
        {
            dt = dt.AddDays(1);
        }

        return dt;
    }

    /// <summary>
    /// 获取当前月份的最后一天
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当前月份的最后一天</returns>
    public static DateTime EndOfMonth(this DateTime @this)
    {
        return new DateTime(@this.Year, @this.Month, DaysInMonth(@this)).EndOfDay();
    }

    /// <summary>
    /// 获取当前月份的最后一天，并指定星期几
    /// </summary>
    /// <param name="this">日期</param>
    /// <param name="dayOfWeek">指定的星期几</param>
    /// <returns>当前月份的最后一天</returns>
    public static DateTime EndOfMonth(this DateTime @this, DayOfWeek dayOfWeek)
    {
        var dt = @this.EndOfMonth();
        while (dt.DayOfWeek != dayOfWeek)
        {
            dt = dt.AddDays(-1);
        }

        return dt;
    }

    /// <summary>
    /// 判断日期是否为今天
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>如果是今天则返回 true，否则返回 false</returns>
    public static bool IsToday(this DateTime @this)
    {
        return @this.Date.Equals(DateTime.Today.Date);
    }

    /// <summary>
    /// 设置时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <param name="hours">小时</param>
    /// <param name="minutes">分钟</param>
    /// <param name="seconds">秒</param>
    /// <returns>设置后的日期时间</returns>
    public static DateTime SetTime(this DateTime @this, int hours, int minutes, int seconds)
    {
        return @this.SetTime(new TimeSpan(hours, minutes, seconds));
    }

    /// <summary>
    /// 设置时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <param name="hours">小时</param>
    /// <param name="minutes">分钟</param>
    /// <param name="seconds">秒</param>
    /// <param name="milliseconds">毫秒</param>
    /// <returns>设置后的日期时间</returns>
    public static DateTime SetTime(this DateTime @this, int hours, int minutes, int seconds, int milliseconds)
    {
        return @this.SetTime(new TimeSpan(0, hours, minutes, seconds, milliseconds));
    }

    /// <summary>
    /// 设置秒和毫秒为0
    /// </summary>
    /// <param name="this">日期时间</param>
    /// <returns>设置后的日期时间</returns>
    public static DateTime TrimSecond(this DateTime @this)
    {
        return @this.SetTime(new TimeSpan(0, @this.Hour, @this.Minute, 0, 0));
    }

    /// <summary>
    /// 设置毫秒为0
    /// </summary>
    /// <param name="this">日期时间</param>
    /// <returns>设置后的日期时间</returns>
    public static DateTime TrimMillisecond(this DateTime @this)
    {
        return @this.SetTime(new TimeSpan(0, @this.Hour, @this.Minute, @this.Second, 0));
    }

    /// <summary>
    /// 设置时间
    /// </summary>
    /// <param name="this">日期</param>
    /// <param name="span">时间跨度</param>
    /// <returns>设置后的日期时间</returns>
    public static DateTime SetTime(this DateTime @this, TimeSpan span)
    {
        return @this.Date.Add(span);
    }

    /// <summary>
    /// 获取自1970年1月1日以来的秒数
    /// </summary>
    /// <param name="this">日期时间</param>
    /// <returns>秒数</returns>
    public static long SecondsSince1970(this DateTime @this)
    {
        TimeSpan ts = @this.ToUniversalTime().Subtract(Jan1St1970);
        return (long)ts.TotalSeconds;
    }

    /// <summary>
    /// 获取明天的日期
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>明天的日期</returns>
    public static DateTime Tomorrow(this DateTime @this)
    {
        return @this.AddDays(1);
    }

    /// <summary>
    /// 获取昨天的日期
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>昨天的日期</returns>
    public static DateTime Yesterday(this DateTime @this)
    {
        return @this.AddDays(-1);
    }

    /// <summary>
    /// 判断该日期中的年份是否为闰年
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>如果是闰年则返回 true，否则返回 false</returns>
    public static bool IsLeapYear(this DateTime @this)
    {
        return @this.Year % 4 == 0 && (@this.Year % 100 != 0 || @this.Year % 400 == 0);
    }

    /// <summary>
    /// 获取程序集的最后修改时间
    /// </summary>
    /// <param name="this">程序集</param>
    /// <returns>最后修改时间</returns>
    public static DateTime LastWriteTime(this Assembly @this)
    {
        return File.GetLastWriteTime(@this.Location);
    }

    /// <summary>
    /// 获取当前日期所在年份的第一天
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当前年份的第一天</returns>
    public static DateTime StartOfYear(this DateTime @this)
    {
        return new DateTime(@this.Year, 1, 1);
    }

    /// <summary>
    /// 获取当前日期所在年份的最后一天
    /// </summary>
    /// <param name="this">日期</param>
    /// <returns>当前年份的最后一天</returns>
    public static DateTime EndOfYear(this DateTime @this)
    {
        return new DateTime(@this.Year, 12, 31).EndOfDay();
    }

    /// <summary>
    /// 获取从指定日期时间到现在的时间间隔
    /// </summary>
    /// <param name="this">日期时间</param>
    /// <returns>时间间隔</returns>
    public static TimeSpan Elapsed(this DateTime @this)
    {
        return DateTime.Now - @this;
    }

    /// <summary>
    /// 将当前时间转换为目标时区的时间
    /// </summary>
    /// <param name="this">当前时间</param>
    /// <param name="destinationTimeZone">目标时区</param>
    /// <returns>转换后的时间</returns>
    public static DateTime ConvertTime(this DateTime @this, TimeZoneInfo destinationTimeZone)
    {
        return TimeZoneInfo.ConvertTime(@this, destinationTimeZone);
    }

    /// <summary>
    /// 将当前时间从源时区转换为目标时区的时间
    /// </summary>
    /// <param name="this">当前时间</param>
    /// <param name="sourceTimeZone">源时区</param>
    /// <param name="destinationTimeZone">目标时区</param>
    /// <returns>转换后的时间</returns>
    public static DateTime ConvertTime(this DateTime @this, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
    {
        return TimeZoneInfo.ConvertTime(@this, sourceTimeZone, destinationTimeZone);
    }

    /// <summary>
    /// 将当前时间转换为UTC时间
    /// </summary>
    /// <param name="this">当前时间</param>
    /// <returns>转换后的UTC时间</returns>
    public static DateTime ConvertTimeToUtc(this DateTime @this)
    {
        return TimeZoneInfo.ConvertTimeToUtc(@this);
    }

    /// <summary>
    /// 将当前时间从源时区转换为UTC时间
    /// </summary>
    /// <param name="this">当前时间</param>
    /// <param name="sourceTimeZone">源时区</param>
    /// <returns>转换后的UTC时间</returns>
    public static DateTime ConvertTimeToUtc(this DateTime @this, TimeZoneInfo sourceTimeZone)
    {
        return TimeZoneInfo.ConvertTimeToUtc(@this, sourceTimeZone);
    }

    /// <summary>
    /// 将UTC时间转换为目标时区的时间
    /// </summary>
    /// <param name="this">UTC时间</param>
    /// <param name="destinationTimeZone">目标时区</param>
    /// <returns>转换后的时间</returns>
    public static DateTime ConvertTimeFromUtc(this DateTime @this, TimeZoneInfo destinationTimeZone)
    {
        return TimeZoneInfo.ConvertTimeFromUtc(@this, destinationTimeZone);
    }
}
